package org.zhaya.sdk.processors;

import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.logz.sawmill.*;
import io.logz.sawmill.annotations.ProcessorProvider;
import io.logz.sawmill.exceptions.ProcessorConfigurationException;
import io.logz.sawmill.utilities.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.zhaya.sdk.util.JacksonUtil;

import javax.inject.Inject;
import java.util.List;
import java.util.Map;

import static java.util.Objects.requireNonNull;

@ProcessorProvider(type = "jsonSort", factory = JsonSortProcessor.Factory.class)
public class JsonSortProcessor implements Processor {

    private final List<String> fieldList;
    private final List<Template> targetFieldList;
    private final List<String> typeList;
    private final List<String> sortTypeList;

    public JsonSortProcessor(List<String> fieldList, List<Template> targetFieldList, List<String> typeList, List<String> sortTypeList) {
        this.fieldList = fieldList;
        this.targetFieldList = targetFieldList;
        this.typeList = typeList;
        this.sortTypeList = checkSortType(sortTypeList);
    }

    public static List<String> checkSortType(List<String> sortTypeList) {
        // sort type ：asc/desc
        boolean checkFlag = false;
        for (String sortType : sortTypeList) {
            if (Config.asc.name().equals(sortType) || Config.desc.name().equals(sortType)) {
                checkFlag = true;
            }
        }
        if (!checkFlag) {
            throw new NullPointerException("sortType not found, sortType that can be selected [asc, desc]");
        }
        return sortTypeList;
    }

    @Override
    public ProcessResult process(Doc doc) {
        String field;
        Template targetField;
        String type;
        String sortType;
        if (fieldList.size() > 10) {
            return ProcessResult.failure("The number of processes is limited to 10.");
        }
        for (int i = 0; i < fieldList.size(); i++) {
            field = fieldList.get(i);
            targetField = targetFieldList.get(i);
            type = typeList.get(i);
            sortType = sortTypeList.get(i);
            if (!doc.hasField(field)) {
                return ProcessResult.failure(String.format("field [%s] is missing", field));
            }
            Object fieldValue = doc.getField(field);
            if (Config.jsonString.name().equals(type)) {
                if (targetField != null) {
                    doc.addField(targetField.render(doc), JacksonUtil.toJSONString(sortJson(fieldValue, sortType)));
                } else {
                    doc.addField(field, JacksonUtil.toJSONString(sortJson(fieldValue, sortType)));
                }
            } else if (Config.jsonObject.name().equals(type)) {
                if (targetField != null) {
                    doc.addField(targetField.render(doc), sortJson(fieldValue, sortType));
                } else {
                    doc.addField(field, sortJson(fieldValue, sortType));
                }
            } else {
                return ProcessResult.failure("type not found, type that can be selected [jsonstring, jsonobject]");
            }
        }
        return ProcessResult.success();
    }

    private Map<String, Object> sortJson(Object fieldValue, String sortType) {
        Map<String, Object> map;
        if (fieldValue instanceof String) {
            try {
                map = JacksonUtil.json2map((String) fieldValue);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (MapUtil.isEmpty(map)) {
                return null;
            }
        } else {
            map = (Map<String, Object>) fieldValue;
        }
        Map<String, Object> sortMap = Maps.newTreeMap((front, last) -> {
            if (Config.asc.name().equals(sortType)) {
                return front.compareTo(last);
            } else {
                return last.compareTo(front);
            }
        });
        sortMap.putAll(map);
        return sortMap;
    }

    public enum Config {
        field,
        targetField,
        type,
        sortType,
        asc,
        desc,
        jsonObject,
        jsonString
    }

    public static class Factory implements Processor.Factory {
        private final TemplateService templateService;

        @Inject
        public Factory(TemplateService templateService) {
            this.templateService = templateService;
        }

        @Override
        public JsonSortProcessor create(Map<String, Object> config) {

            Configuration jsonSortConfig = JsonUtils.fromJsonMap(Configuration.class, config);

            List<Map<String, Object>> jsonSortConfigList = jsonSortConfig.getConfig();

            List<String> fieldList = Lists.newArrayList();
            List<Template> targetFieldList = Lists.newArrayList();
            List<String> typeList = Lists.newArrayList();
            List<String> sortTypeList = Lists.newArrayList();

            if (CollectionUtils.isEmpty(jsonSortConfigList)) {
                throw new ProcessorConfigurationException("failed to parse json sort processor config, could not resolve config path");
            } else {
                jsonSortConfigList.forEach(jsonSort -> {
                    fieldList.add(requireNonNull((String) jsonSort.get(Config.field.name()), "field cannot be null"));
                    targetFieldList.add(StringUtils.isEmpty((String) jsonSort.get(Config.targetField.name())) ? null : templateService.createTemplate((String) jsonSort.get(Config.targetField.name())));
                    typeList.add(requireNonNull((String) jsonSort.get(Config.type.name()), "type cannot be null"));
                    sortTypeList.add(requireNonNull((String) jsonSort.get(Config.sortType.name()), "sortType cannot be null"));
                });
            }
            return new JsonSortProcessor(fieldList, targetFieldList, typeList, sortTypeList);
        }
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Configuration implements Processor.Configuration {
        private List<Map<String, Object>> config;

    }
}